#include "stdafx.h"
#include "GcManager.h"
#include <process.h>
#include <windows.h>
#include "GcTool.h"
#include "SmartPtrContext.h"

static const DWORD MS_VC_EXCEPTION=0x406D1388;

#pragma pack(push,8)
typedef struct tagTHREADNAME_INFO
{
	DWORD dwType; // Must be 0x1000.
	LPCSTR szName; // Pointer to name (in user addr space).
	DWORD dwThreadID; // Thread ID (-1=caller thread).
	DWORD dwFlags; // Reserved for future use, must be zero.
} THREADNAME_INFO;
#pragma pack(pop)

static void SetThreadName( DWORD dwThreadID, char* threadName)
{
	THREADNAME_INFO info;
	info.dwType = 0x1000;
	info.szName = threadName;
	info.dwThreadID = dwThreadID;
	info.dwFlags = 0;

	__try
	{
		RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info );
	}
	__except(EXCEPTION_EXECUTE_HANDLER)
	{
	}
}

unsigned __stdcall CGcManager::GcThreadFunc( void * )
{
	while (1)
	{
		// wait for the signal to end the thread
		DWORD waitStop = WaitForSingleObject(CSmartPtrContext::GetStopGcThreadEvent(), 500);
		if (waitStop == WAIT_OBJECT_0)
		{
			// stop the thread
			break;
		}
		else if (waitStop == WAIT_TIMEOUT)
		{
			// do the GC work here
			// first check whether we need GC now
			DWORD needGC = WaitForSingleObject(CSmartPtrContext::GetNeedGcEvent(), 500);
			if (needGC == WAIT_TIMEOUT)
			{
				// do not GC
				continue;
			}
			else if (needGC == WAIT_OBJECT_0)
			{
				// do GC now
				// TODO:stop all other threads

				CSmartPtrContext::SetIsCollecting(true);

				// how many GC has been continuously called
				int gcTimes = 1;

				int needGcGeneration = CSmartPtrContext::GetNeedGcGeneration();
				
				while(gcTimes > 0)
				{
					if (gcTimes == GENERATIONS_NUM + 1)
					{
						// this case will be happened once in one condition, 
						// that is the first generation is from g0, and continuous GENERATIONS_NUM
						// GC has been done, which means g2 has been collected once, but do not 
						// collect enough space, in this case, throw an exception
						
						// TODO: throw exceptions
					}

					// get all the smart ptrs that needs to be GCed
					list<LinkNode*> needGcList;
					CSmartPtrManager::GetInstance()->CollectNeedGcPtrs(
						needGcGeneration, 
						needGcList);

					// classify user reachable ptrs and embedded ptrs
					list<LinkNode*> userReachablePtrsList, embeddedPtrsList;
					CGcTool::ClassifySmartPtrs(needGcList, userReachablePtrsList, embeddedPtrsList);

					// mark ptrs
					list<LinkNode*> usingPtrsList;
					CGcTool::Mark(userReachablePtrsList, embeddedPtrsList, usingPtrsList);

					// delete the useless ptrs
					CSmartPtrContext::SetLastGcCollectedObjs( 
						CGcTool::DoGcWork(embeddedPtrsList) );

					// shift the remaining ptrs to the next generation
					// combine the remaining list
					int needAnotherGcGeneration = -1;
					int iNeedAnotherGC = 
						CSmartPtrManager::GetInstance()->ShiftGcSurvivePtrs(
						needGcGeneration, 
						usingPtrsList);

					// check whether need another GC
					if (iNeedAnotherGC != -1)
					{
						// need another GC
						gcTimes++;
						needGcGeneration = iNeedAnotherGC;
					}
					else
					{
						// iNeedAnotherGC == -1, do not need another GC
						gcTimes = 0;
					}
				}
				
				CSmartPtrContext::SetIsCollecting(false);

				//TODO: resume other threads
			}
		}
		else
		{
			//TODO: throw exception
		}		
	}

	// inform the main thread the GC thread is ended.
	SetEvent(CSmartPtrContext::GetGcThreadAlreadyStoppedEvent());

	return 0;
}

void CGcManager::StartGcThread()
{
	UINT threadID = 0;

	HANDLE hGcWork = (HANDLE)_beginthreadex(NULL, 0, GcThreadFunc, NULL, 0, &threadID);
	if (hGcWork == 0)
	{
		// TODO:
		// CExceptionHelper::Throw(DREPLAY_E_CREATE_SPIDCONTROLLERMANAGER_THREAD, GetLastError());
	}

	SetThreadName(threadID, "GC_Thread");
}

void CGcManager::EndGcThread()
{
	// set the signal to inform the working thread to stop
	SetEvent(CSmartPtrContext::GetStopGcThreadEvent() );
}
